﻿using System;
using System.Collections.Generic;
using System.Text;
using CodePlex.Resourceful;
using CodePlex.Resourceful.Microsoft.Azure;
using System.Collections;
using System.Net;
using System.Net.Sockets;

namespace CodePlex.SpaceBlock.FS
{

    public class AzureBlobStorageFileSystem : BaseFileSystem, IFileSystem, ISupportsTransferWithMetadata,
        ISupportsCheckAction
    {

        private readonly static string PlaceholderFileName = ":SpaceBlockPlaceholder:";

        private readonly AzureBlobStorageConnection _connection;


        public AzureBlobStorageFileSystem(AzureBlobStorageConnection connection)
        {
            _connection = connection;
        }
        public Tracer Tracer
        {
            get { return _connection.Tracer; }
        }
        public AzureBlobStorageConnection Connection
        {
            get { return _connection; }
        }

        
        public bool AllowNewFolder(Path folder)
        {
            return folder.Level >= 0 ;
        }
        public bool AllowDeleteFolder(Path folder)
        {
            return folder.Level > 0;
        }
       

        public bool IsContainer(Path path)
        {
            return path != null && path.IsFolder && path.Level == 1;
        }


        private bool? _isValidated;

        private void EnsureValid()
        {
            if (_isValidated == null)
            {
                if (!this.IsDevStorage)
                {
                    _connection.ListContainers();
                }

                _isValidated = true;
            }
        }

        public string Caption
        {
            get
            {
                this.EnsureValid(); // TODO expected side effect = bad
                return string.Format("Azure Blob Storage File System - {0}", this.ShortCaption);
            }
        }


        private bool IsDevStorage
        {
            get
            {
                return _connection.Client.AccountName == AzureConstants.DevelopmentStorageAccountName;
            }
        }

        public string ShortCaption
        {
            get
            {
                if (this.IsDevStorage)
                {
                    return "Development Storage";
                }
                

                return _connection.Client.AccountName  + (_connection.Client.IsSSL ? " (ssl)" : "");
            }
        }


        public Uri GetBlobUri(Path file)
        {
            this.CheckNull(file);
            this.CheckFile(file);

            PathInfo info = new PathInfo(file);
            return _connection.GetBlobUri(info.ContainerName, info.BlobName);

        }


        public bool Exists(Path path)
        {
            this.CheckNull(path);

            if (path.Level == 0)
            {
                return true;
            }

           
            PathInfo info = new PathInfo(path);

            if (path.Level == 1)
            {
                return path.IsFolder && _connection.IsContainerPresent(info.ContainerName);
            }
            if (path.IsFolder)
            {
                return !_connection.ListBlobs(info.ContainerName, info.BlobName, null, null, 1).IsEmpty;
            }
            else
            {
                return _connection.IsBlobPresent(info.ContainerName, info.BlobName);
            }
        }




        public ReadOnlyList<IPathItem> GetItems(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            List<IPathItem> rt = new List<IPathItem>();

            if (path.Level == 0)
            {
                ContainerListing containers = _connection.ListContainers();
               
                foreach (ContainerInfo c in containers.Containers)
                {
                    DateTime lastModified = DateTime.Parse(c.LastModified);
                    Path child = path.Combine(c.Name + Path.Separator);
                    rt.Add(new FolderItem(this, child, lastModified));
                }
                return new ReadOnlyList<IPathItem>(rt);
            }

          


            PathInfo info = new PathInfo(path);


            string nextMarker = null;

            do
            {

                BlobListing listing = _connection.ListBlobs(info.ContainerName, info.BlobName, nextMarker, Path.Separator, null);

                string containerUriString = _connection.GetContainerUri(info.ContainerName).ToString();

                foreach (BlobInfo b in listing.Blobs)
                {

                    string rest = b.BlobUri.ToString().Substring(containerUriString.Length + 1);

                    string name = rest.Substring(info.BlobName.Length);

                    if (name.Equals(PlaceholderFileName))
                    {
                        continue;
                    }

                    Path child = path.Combine(name);

                    rt.Add(new FileItem(this, b, child));

                }

                foreach (string prefix in listing.BlobPrefixes)
                {
                    Path child = Path.Parse(Path.Separator + info.ContainerName + Path.Separator + prefix);
                    rt.Add(new FolderItem(this, child, null));
                }

                nextMarker = listing.NextMarker;

            } while (nextMarker != null);
            

            return new ReadOnlyList<IPathItem>(rt);
        }


        public ReadOnlyList<IFolderItem> GetFolders(Path path)
        {
            return new ReadOnlyList<IFolderItem>(Utils.OfType<IFolderItem>(this.GetItems(path)));
        }

        

        public ReadOnlyList<IFileItem> GetFiles(Path path)
        {
            return new ReadOnlyList<IFileItem>(Utils.OfType<IFileItem>(this.GetItems(path)));
        }

     


        public IPathItem GetItem(Path path)
        {
            this.CheckNull(path);


            if (!this.Exists(path))
            {
                return null;
            }

            if (path.IsFolder)
            {
                return new FolderItem(this, path, null);
            } 
            else
            {
                PathInfo info = new PathInfo(path);

                Uri blobUri = _connection.GetBlobUri(info.ContainerName, info.BlobName);

                NameValuePairs metadata = _connection.GetBlobProperties(info.ContainerName,info.BlobName);


                BlobInfo blob = new BlobInfo(blobUri,
                    metadata[HttpHeader.LastModified],
                    metadata[HttpHeader.ETag],
                    long.Parse(metadata[HttpHeader.ContentLength]),
                    metadata.GetIfExists(HttpHeader.ContentType),
                    metadata.GetIfExists(HttpHeader.ContentEncoding),
                    metadata.GetIfExists(HttpHeader.ContentLanguage));


                return new FileItem(this, blob, path);

            }

          


        }

        public void EnsureFolderExists(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            if (this.Exists(path))
            {
                return;
            }

            if (path.Level == 1)
            {
                string containerName = path.Name;
                _connection.CreateContainer(containerName, null);

            }
            else
            {
                PathInfo info = new PathInfo(path);
                _connection.PutBlob(info.ContainerName, info.BlobName + PlaceholderFileName, "");
            }

           

        }

        public void EnsureFileExists(Path path)
        {
            this.CheckNull(path);
            this.CheckFile(path);

            if (path.Level == 1)
            {
                throw CodePlexException.Format("Top-level files are not supported, only folders (containers).");
            }

            PathInfo info = new PathInfo(path);
            if (_connection.IsBlobPresent(info.ContainerName, info.BlobName))
            {
                return;
            }
            _connection.PutBlob(info.ContainerName, info.BlobName, "");
        }

        public void Delete(Path path)
        {
            this.CheckNull(path);

            if (path.Level.Equals(Path.Root))
            {
                throw CodePlexException.Format("Cannot delete root!");
            }

             PathInfo info = new PathInfo(path);

            if (path.IsFolder)
            {
                if (path.Level == 1)
                {
                    _connection.DeleteContainer(info.ContainerName);
                   
                }
                else
                {

                    foreach (BlobInfo b in this.ListFolderContents(info))
                    {
                        _connection.DeleteBlob(info.ContainerName, GetBlobName(info.ContainerName,b.BlobUri));
                    }
                    

                }
            }
            else
            {
                _connection.DeleteBlob(info.ContainerName, info.BlobName);
            }
        }





        public Metadata GetMetadata(Path path)
        {
            Metadata rt = new Metadata();

            this.AddMetadata(rt, BaseMetadataKeys.Path, path.ToString());
            this.AddMetadata(rt, BlobStorageMD.AccountName, _connection.Client.AccountName);
            if (path.Level == 0)
            {
                this.AddMetadata(rt, BaseMetadataKeys.Type, "File System");
              //  this.AddMetadata(rt, SSDSMD.Username, this.SSDSConnection.Username);
                return rt;
            }

            PathInfo info = new PathInfo(path);


            this.AddMetadata(rt, BlobStorageMD.ContainerName, info.ContainerName);

            if (path.Level == 1)
            {
                this.AddMetadata(rt, BaseMetadataKeys.Type, "Virtual Folder");

                bool isPublicAccess = _connection.GetContainerACL(info.ContainerName);
                this.AddMetadata(rt, AzureConstants.Headers.MicrosoftPropertyPublicAccess, isPublicAccess.ToString());


                NameValuePairs containerProperties = _connection.GetContainerProperties(info.ContainerName);
                foreach(string key in containerProperties.Keys)
                {
                    this.AddMetadata(rt, key, containerProperties[key]);
                }

                return rt;

            }


            if (path.IsFolder)
            {
                this.AddMetadata(rt, BaseMetadataKeys.Type, "Virtual Folder");
                this.AddMetadata(rt, BlobStorageMD.BlobPrefix, info.BlobName);
                
            }
            else
            {
                this.AddMetadata(rt, BaseMetadataKeys.Type, "Virtual File");
                this.AddMetadata(rt, BlobStorageMD.BlobName, info.BlobName);


                NameValuePairs properties = _connection.GetBlobProperties(info.ContainerName, info.BlobName);

                //Entity entity = _connection.GetEntity(_authority, info.Container, info.GetFileEntityID());

                //this.AddMetadata(rt, SSDSMD.Kind, entity.Kind);
                //this.AddMetadata(rt, SSDSMD.Version, entity.Version.ToString());


                foreach (string key in properties.Keys)
                {
                    string value = properties[key];

                    if (key == HttpHeader.ContentLength)
                    {
                        value = Utils.ComputeSizeString(Convert.ToInt64(value));
                    }

                    this.AddMetadata(rt, key, value);
                }



            }

            return rt;
        }

        override protected string GetMetadataCategory(string key)
        {

            if (Utils.In(key,
                BlobStorageMD.AccountName,
                BlobStorageMD.ContainerName,
                BlobStorageMD.BlobName,
                BlobStorageMD.BlobPrefix
                

                ))
            {
                return "Azure Blob Storage";
            }

            if (Utils.In(key,HttpHeader.RequestHeaders) || Utils.In(key,HttpHeader.ResponseHeaders))
            {
                return "HTTP";
            }

            return base.GetMetadataCategory(key);
        }

        override protected string GetMetadataDescription(string key, string category)
        {

            NameValuePairs descriptions = new NameValuePairs();

            descriptions[BlobStorageMD.AccountName] = "The account name associated with the Azure storage project.";
            descriptions[BlobStorageMD.ContainerName] = "A container is a set of blobs.  Note that containers are not nested, so it's not possible for one container to contain another container.";
            descriptions[BlobStorageMD.BlobName] = " A blob is any single entity comprised of binary data, such as a file or an image.";
            descriptions[BlobStorageMD.BlobPrefix] = "A common prefix for blobs in this container.";
            descriptions[AzureConstants.Headers.MicrosoftPropertyPublicAccess] = @"Indicates whether a container may be accessed publicly. Returns ""False"" if container access is private, and ""True"" if container access is public.";

            return descriptions.GetIfExists(key) ?? base.GetMetadataDescription(key, category);
        }



        public void Transfer(IFileItem source, Path destination, Action<long> bytesTransferred)
        {
            this.Transfer(source, destination, null, bytesTransferred);
        }

        public void Transfer(IFileItem source, Path destination, NameValuePairs metadata, Action<long> bytesTransferred)
        {

            metadata = metadata ?? new NameValuePairs();
            metadata = new NameValuePairs(metadata);

            PathInfo info = new PathInfo(destination);

            
            metadata[HttpHeader.ContentLength] = source.Size.ToString();

            _connection.PutBlob(info.ContainerName, info.BlobName, metadata, delegate
            {
                return source.GetInputStream();
            }, true,bytesTransferred);
         
            

        }






















        private string GetBlobName(string containerName, Uri blobUri)
        {
            return blobUri.ToString().Substring(_connection.GetContainerUri(containerName).ToString().Length + 1);
        }

        private IEnumerable<BlobInfo> ListFolderContents(PathInfo folderInfo)
        {
            BlobListing listing = _connection.ListBlobs(folderInfo.ContainerName, folderInfo.BlobName, null, null, null);
            return listing.Blobs;
        }


        private static class BlobStorageMD
        {
            public static readonly string AccountName = "AccountName";
            public static readonly string ContainerName = "ContainerName";
            public static readonly string BlobName = "BlobName";
            public static readonly string BlobPrefix = "BlobPrefix";


        }

        private class PathInfo
        {
            private readonly string _containerName;
            private readonly string _blobName;

            public PathInfo(Path path)
            {
                _containerName = path.Parts[0];

                _blobName = path.ToString().Substring(_containerName.Length + 2);
                
            }

            public string ContainerName { get { return _containerName; } }
            public string BlobName { get { return _blobName; } }
        }

        private class FileItem : IFileItem
        {
            private readonly AzureBlobStorageFileSystem _fs;
            private readonly BlobInfo _blob;
            private readonly Path _path;

            public FileItem(AzureBlobStorageFileSystem fs, BlobInfo blob, Path path)
            {
                _fs = fs;
                _blob = blob;
                _path = path;
            }
            public long Size
            {
                get { return _blob.Size; }
            }


            public System.IO.Stream GetInputStream()
            {
                PathInfo info = new PathInfo(_path);

                return _fs._connection.GetBlobAsStream(info.ContainerName, info.BlobName);

            }

            public IFileSystem FileSystem
            {
                get { return _fs; }
            }

            public Path Path
            {
                get { return _path; }
            }

            public DateTime? LastModified
            {
                get { return DateTime.Parse(_blob.LastModified); }
            }
        }


      
    }
}
